package me.cabbit.digitone.tones.speedlines;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;

import me.cabbit.digitone.support.NoiseGenerator;
import me.cabbit.digitone.tones.Screentone;

import org.w3c.dom.Element;

/**
 * Implements the speedline screentone. For now only circular
 * but I suppose this could be adapted to do parallel lines
 * as well
 * 
 * @author Arkaid
 *
 */
public abstract class Speedlines extends Screentone {
	
	//------------------------------------------------------------------//
	//--------------------------- properties ---------------------------//
	//------------------------------------------------------------------//

	/** Horizontal radius for the empty space in the center */
	private int radius_a;
	
	/** Vertical radius for the empty space in the center */
	private int radius_b;
	
	/** Center of the empty space */
	private int cx;
	
	/** Center of the empty space */
	private int cy;
	
	/** Number of lines to use */
	private int nLines;
	
	/** Maximum width of the line (at the thickest part) */
	private int maxWidth;
	
	/** Seed used for the noise generator */ 
	private long randomSeed;
	
	/** Noise generator */
	private NoiseGenerator noise;
	
	/** How much to displace each line according to the noise */
	private int displacement;
	
	//------------------------------------------------------------------//
	//----------------------- setters and getters ----------------------//
	//------------------------------------------------------------------//

	/** Sets the horizontal radius for the empty ellipse at the center */
	public void setRadiusA(int radiusA)
	{
		this.radius_a = radiusA;
	}
	
	/** Gets the horizontal radius for the empty ellipse at the center */
	public int getRadiusA()
	{
		return this.radius_a;
	}
	
	/** Sets the vertical radius for the empty ellipse at the center */
	public void setRadiusB(int radiusB)
	{
		this.radius_b = radiusB;
	}
	
	/** Gets the vertical radius for the empty ellipse at the center */
	public int getRadiusB()
	{
		return this.radius_b;
	}
	
	/** Sets the x center for the empty ellipse at the center */
	public void setCenterX(int cx)
	{
		this.cx = cx;
	}
	
	/** Gets the x center for the empty ellipse at the center */
	public int getCenterX()
	{
		return this.cx;
	}
	
	/** Sets the y center for the empty ellipse at the center */
	public void setCenterY(int cy)
	{
		this.cy = cy;
	}
	
	/** Gets the y center for the empty ellipse at the center */
	public int getCenterY()
	{
		return this.cy;
	}	
	
	/** Sets number of lines to use for the screentone */
	public void setNumberOfLines(int nLines)
	{
		this.nLines = nLines;
	}
	
	/** Gets number of lines to use for the screentone */
	public int getNumberOfLines() 
	{
		return this.nLines;
	}
	
	/** Sets the width of the each line at its thickest point */
	public void setMaxWidth(int maxWidth)
	{
		this.maxWidth = maxWidth;
	}
	
	/** Gets the width of the each line at its thickest point */
	public int getMaxWidth() 
	{
		return this.maxWidth;
	}	
	
	/** According to a noise function, the lines get displaced
	 *  This value sets the maximum possible displacement */
	public void setDisplacement(int displacement)
	{
		this.displacement = displacement;
	}
	
	/** Gets the maximum possible displacement for the lines */
	public int getDisplacement() 
	{
		return this.displacement;
	}
	
	/** Sets the seed for the noise generator and resets it */
	public void setSeed(long seed)
	{
		this.randomSeed = seed;
		this.noise = new NoiseGenerator(10, 1.0, seed);
	}
	
	/** Gets the seed for the noise generator */
	public double getSeed()
	{
		return this.randomSeed;
	}
	
	//------------------------------------------------------------------//
	//----------------------------- methods ----------------------------//
	//------------------------------------------------------------------//

	/** Constructor. Sets default values */
	public Speedlines() {
		this.radius_a = 200;
		this.radius_b = 180;
		
		this.cx = 360;
		this.cy = 350;
		
		this.nLines = 90;
		this.maxWidth = 15;
		
		this.randomSeed = 567892165446L;
		this.noise = new NoiseGenerator(10, 1.0, this.randomSeed);
		this.displacement = 0;
	}
	
	@Override
	public void savePreset(Element node) 
	{
		node.setAttribute("radius_a", String.valueOf(this.radius_a));
		node.setAttribute("radius_b", String.valueOf(this.radius_b));
		node.setAttribute("cx", String.valueOf(this.cx));
		node.setAttribute("cy", String.valueOf(this.cy));
		node.setAttribute("nLines", String.valueOf(this.nLines));
		node.setAttribute("maxWidth", String.valueOf(this.maxWidth));
		node.setAttribute("randomSeed", String.valueOf(this.randomSeed));
		node.setAttribute("displacement", String.valueOf(this.displacement));
	}

	@Override
	public void loadPreset(Element node) 
	{
		this.radius_a = Integer.parseInt(node.getAttribute("radius_a"));
		this.radius_b = Integer.parseInt(node.getAttribute("radius_b"));
		this.cx = Integer.parseInt(node.getAttribute("cx"));
		this.cy = Integer.parseInt(node.getAttribute("cy"));
		this.nLines = Integer.parseInt(node.getAttribute("nLines"));
		this.maxWidth = Integer.parseInt(node.getAttribute("maxWidth"));
		this.randomSeed = Long.parseLong(node.getAttribute("randomSeed"));
		this.displacement = Integer.parseInt(node.getAttribute("displacement"));
	}

	@Override
	public void render(Graphics2D target) 
	{
		//fill the background with a white color and 
		//set black to draw the lines
		target.setColor(Color.white);
		target.fillRect(0, 0, getWidth(), getHeight());
		target.setColor(Color.black);
		
		//shorthand
		int w = this.getWidth();
		int h = this.getHeight();
		
		//draw lines arranged in an ellipse
		double PI2 = Math.PI * 2;
		double step = PI2 / this.nLines;
		//make sure that the lines reach all the way to the border
		int diagonal = (int)Math.sqrt(w*w + h*h);
		//for each angle step...
		for (double angle = 0; angle < PI2; angle+=step)
		{
			//calculate sin and cos for the current angle
			double sin = Math.sin(angle);
			double cos = Math.cos(angle);
			
			//how much to displace depends of the value 
			//returned by the noise function multiplied by
			//the maximum displacement value
			double phase = this.noise.getSample(angle / PI2) * this.displacement;
			
			//how much to displace in each direction
			int px = (int) (phase * cos);
			int py = (int) (phase * sin);
			
			//the starting point on the ellipse will be
			//the center plus the displacement, modified
			//by the radius in each direction
			int sx = (int) (this.cx + px + this.radius_a * cos);
			int sy = (int) (this.cy + py + this.radius_b * sin);
			
			//the end point is the center, plus the size
			//of the diagonal. This should be enough
			//to reach the borders of the image
			int ex = (int) (this.cx + diagonal * cos);
			int ey = (int) (this.cy + diagonal * sin);
			
			//draw the line
			drawLine(target, ex, ey, sx, sy, this.maxWidth);
		}

	}

	/**
	 * draws a triangular shape between two points, starting at startWidth
	 * and slowly decreases until it reaches the endpoint
	 * 
	 * @param target the Graphics2D object to draw into
	 * @param x0 the starting point (x)
	 * @param y0 the starting point (y)
	 * @param x1 the end point (x)
	 * @param y1 the end point (y)
	 * @param startWidth the thickness at the start point
	 */
	protected void drawLine(Graphics2D target, int x0, int y0, int x1, int y1, int startWidth)
	{
		//first determine the size in each direction
		double dx = x1 - x0;
		double dy = y1 - y0;
		
		//calculate length of the line
		double length = Math.sqrt(dx*dx + dy*dy);
		
		//normalize to get a direction vector
		dx /= length;
		dy /= length;
		
		//to draw in the target
		Polygon line = new Polygon();
		
		//calculate 3 points of the triangle
		int x, y;
		//get a perpendicular vector to the line and 
		//multiply it by the starting width to get the base
		x = (int) (-dy * startWidth);
		y = (int) ( dx * startWidth);

		//add the base of the triangle 
		line.addPoint(x0 + x, y0 + y);
		line.addPoint(x0 - x, y0 - y);

		//end of the triangle
		line.addPoint(x1, y1);
		
		//draw it
		target.fillPolygon(line);
	}
}
